// This work is licensed under a Attribution-NonCommercial-ShareAlike 4.0 International (CC BY-NC-SA 4.0) https://creativecommons.org/licenses/by-nc-sa/4.0/
// © LuxAlgo

//@version=5
indicator("Sequencer [LuxAlgo]", "LuxAlgo - Sequencer", overlay = true, max_labels_count = 500, max_lines_count = 500)
//---------------------------------------------------------------------------------------------------------------------}
//Settings
//---------------------------------------------------------------------------------------------------------------------{
//Preparation Phase
group1 = 'Preparation Phase'

preparationLen = input.int(9, 'Preparation Phase Length', minval = 2, group = group1)
preparationComparison = input.int(4, 'Comparison Period', minval = 1, group = group1)

preparationType = input.string('Standard', 'Preparation Type', options = ['Refined', 'Standard'], group = group1)
optimalSteps = input('', 'Refined Preparation Steps', group = group1
  , tooltip = 'Specifies the steps evaluated to determine if a preparation phase is refined or not. Multiple steps are supported, and should be comma separated.')

bullishPreparation = input(true, 'Bullish Preparation', inline = 'inline1', group = group1)
bullishPreparationCss = input(#089981, '', inline = 'inline1', group = group1)

bearishPreparation = input(true, 'Bearish Preparation', inline = 'inline2', group = group1)
bearishPreparationCss = input(#f23645, '', inline = 'inline2', group = group1)

preparationLabelSize = input.string('Tiny', 'Label Size', options = ['Tiny', 'Small', 'Normal'], group = group1)

//Leadup Phase
group2 = 'Lead-up Phase'

leadupLen = input.int(13, 'Lead-Up Phase Length', minval = 2, group = group2)
leadupComparison = input.int(2, 'Comparison Period', minval = 1, group = group2)

suspension = input(false, inline = 'inline5', group = group2)
suspensionSteps = input.string('', 'Steps', inline = 'inline5', group = group2
  , tooltip = 'Specifies the steps evaluated to determine if the Lead-Up suspension is respected. Multiple steps are supported, and should be comma separated.')

applyCancellation = input(true, 'Apply Cancellation', group = group2
  , tooltip = 'Cancellation will remove any incomplete Lead-Up upon the completion of a new preparation phase of opposite sentiment')

bullishLeadupPhase = input(true, 'Bullish Lead-Up', inline = 'inline3', group = group2)
bullishLeadupCss = input(#2962ff, '', inline = 'inline3', group = group2)

bearishLeadupPhase = input(true, 'Bearish Lead-Up', inline = 'inline4', group = group2)
bearishLeadupCss = input(#ff5d00, '', inline = 'inline4', group = group2)

leadupLabelSize = input.string('Tiny', 'Label Size', options = ['Tiny', 'Small', 'Normal'], group = group2)

//Levels
group3 = 'Preparation/Leadup Completion Levels'

showBullishPreparationLvl = input(false, 'Bullish Preparation Levels', inline = 'bullish preparation level', group = group3)
bullishPreparationLvlCss = input(#089981, '', inline = 'bullish preparation level', group = group3)
bullishPreparationLvlLast = input(1, 'Show Last', inline = 'bullish preparation level', group = group3)

showBearishPreparationLvl = input(false, 'Bearish Preparation Levels', inline = 'bearish preparation level', group = group3)
bearishPreparationLvlCss = input(#f23645, '', inline = 'bearish preparation level', group = group3)
bearishPreparationLvlLast = input(1, 'Show Last', inline = 'bearish preparation level', group = group3)

showBullishLeadupLvl = input(false, 'Bullish Lead-Up Levels', inline = 'bullish leadup level', group = group3)
bullishLeadupLvlCss = input(#2962ff, '', inline = 'bullish leadup level', group = group3)
bullishLeadupLvlLast = input(1, 'Show Last', inline = 'bullish leadup level', group = group3)

showBearishLeadupLvl = input(false, 'Bearish Lead-Up Levels', inline = 'bearish leadup level', group = group3)
bearishLeadupLvlCss = input(#ff5d00, '', inline = 'bearish leadup level', group = group3)
bearishLeadupLvlLast = input(1, 'Show Last', inline = 'bearish leadup level', group = group3)

//---------------------------------------------------------------------------------------------------------------------}
//Functions
//---------------------------------------------------------------------------------------------------------------------{
n = bar_index

method manage_array(array<label> array_label, highlight, clear, delete, loc_y, label_text, text_color, label_size)=>
    var lbl_size = switch label_size
        'Tiny'   => size.tiny
        'Small'  => size.small
        'Normal' => size.normal

    if delete and array_label.size() > 0
        for i = array_label.size()-1 to 0
            array_label.remove(i).delete()

    //Highlight Bullish Preparation
    if highlight
        array_label.push(
          label.new(n
          , loc_y
          , label_text
          , style = loc_y == low ? label.style_label_up : label.style_label_down
          , color = color(na)
          , textcolor = text_color
          , size = lbl_size))

    if clear
        array_label.clear()

display_level(completion, price, size, css)=>
    var lvls = array.new<line>(0)

    if completion
        lvls.unshift(line.new(n, price, n, price, color = css))
    
        if lvls.size() > size
            lvls.pop().delete()
    
    for lvl in lvls
        lvl.set_x2(n)

//---------------------------------------------------------------------------------------------------------------------}
//Preparation Phase
//---------------------------------------------------------------------------------------------------------------------{
var optimal_steps = str.split(optimalSteps, ',')

var bullish_count = 0
var pending_bullish = array.new<label>(0)
var min_low = 0.

var bearish_count = 0
var pending_bearish = array.new<label>(0)
var max_high = 0.

bullish_count := close < close[preparationComparison] and bullishPreparation ? bullish_count + 1 : 0
bearish_count := close > close[preparationComparison] and bearishPreparation ? bearish_count + 1 : 0

//Track step high/low
if preparationType == 'Refined'
    max_high := bearish_count == 1 ? na : optimal_steps.includes(str.tostring(bearish_count)) ? math.max(high, nz(max_high, high)) : max_high 
    min_low := bullish_count == 1 ? na : optimal_steps.includes(str.tostring(bullish_count)) ? math.min(low, nz(min_low, low)) : min_low 

complete_bullish_preparation = switch preparationType
    'Refined' => low < min_low and bullish_count == preparationLen
    => bullish_count == preparationLen

complete_bearish_preparation = switch preparationType
    'Refined' => high > max_high and bearish_count == preparationLen
    => bearish_count == preparationLen

//Highlight Bullish Preparation
pending_bullish.manage_array(bullish_count > 0 and bullish_count <= preparationLen
  , complete_bullish_preparation
  , bullish_count < bullish_count[1] or (bullish_count == preparationLen and not complete_bullish_preparation)
  , low
  , str.tostring(bullish_count) + (bullish_count == 9 ? '\n▲' : '')
  , bullishPreparationCss
  , preparationLabelSize
  )

//Highlight Bearish Preparation
pending_bearish.manage_array(bearish_count > 0 and bearish_count <= preparationLen
  , complete_bearish_preparation
  , bearish_count < bearish_count[1] or (bearish_count == preparationLen and not complete_bearish_preparation)
  , high
  , (bearish_count == 9 ? '▼\n' : '') + str.tostring(bearish_count)
  , bearishPreparationCss
  , preparationLabelSize
  )

//Display levels
if showBullishPreparationLvl
    display_level(complete_bullish_preparation, low, bullishPreparationLvlLast, bullishPreparationLvlCss)

if showBearishPreparationLvl
    display_level(complete_bearish_preparation, high, bearishPreparationLvlLast, bearishPreparationLvlCss)

//---------------------------------------------------------------------------------------------------------------------}
//Leadup Phase
//---------------------------------------------------------------------------------------------------------------------{
var suspension_steps = str.split(suspensionSteps, ',')

var bullish_leadup = 0
var pending_bullish_leadup = array.new<label>(0)
var float min_close_suspension = na

var bearish_leadup = 0
var pending_bearish_leadup = array.new<label>(0)
var float max_close_suspension = na

if bullishLeadupPhase
    bullish_leadup := applyCancellation and complete_bearish_preparation ? na 
      : complete_bullish_preparation ? 1 
      : close < low[leadupComparison] ? bullish_leadup + 1 
      : bullish_leadup

if bearishLeadupPhase
    bearish_leadup := applyCancellation and complete_bullish_preparation ? na 
      : complete_bearish_preparation ? 1 
      : close > high[leadupComparison] ? bearish_leadup + 1 
      : bearish_leadup

//Suspension
if suspension
    max_close_suspension := bearish_leadup == 1 ? na : suspension_steps.includes(str.tostring(bearish_leadup)) ? math.max(close, nz(max_close_suspension, close)) : max_close_suspension
    min_close_suspension := bullish_leadup == 1 ? na : suspension_steps.includes(str.tostring(bullish_leadup)) ? math.min(close, nz(min_close_suspension, close)) : min_close_suspension

//Highlight Bullish Leadup
complete_bullish_leadup = suspension ?
  bullish_leadup == leadupLen and low <= min_close_suspension
  : bullish_leadup == leadupLen

delete_bullish_leadup = complete_bullish_preparation
  or (applyCancellation and complete_bearish_preparation) 
  or (bullish_leadup == leadupLen and not complete_bullish_leadup)

pending_bullish_leadup.manage_array(
  complete_bullish_preparation or (bullish_leadup > bullish_leadup[1] and bullish_leadup <= leadupLen)
  , complete_bullish_leadup
  , delete_bullish_leadup
  , low
  , (bullish_count > 0 and bullish_count <= preparationLen ? '\n' : '') + (complete_bullish_preparation ? '\n' : '') + str.tostring(bullish_leadup)
  , bullishLeadupCss
  , leadupLabelSize
  )

//Reset leadup count on suspension
if suspension and bullish_leadup == leadupLen and low <= min_close_suspension
    bullish_leadup := na

//Highlight Bearish Leadup
complete_bearish_leadup = suspension ?
  bearish_leadup == leadupLen and high >= max_close_suspension
  : bearish_leadup == leadupLen

delete_bearish_leadup = complete_bearish_preparation
  or (applyCancellation and complete_bullish_preparation) 
  or (bearish_leadup == leadupLen and not complete_bearish_leadup)

pending_bearish_leadup.manage_array(
  complete_bearish_preparation or (bearish_leadup > bearish_leadup[1] and bearish_leadup <= leadupLen)
  , complete_bearish_leadup
  , delete_bearish_leadup
  , high
  , str.tostring(bearish_leadup) + (bearish_count > 0 and bearish_count <= preparationLen ? '\n' : '') + (complete_bearish_preparation ? '\n' : '')
  , bearishLeadupCss
  , leadupLabelSize
  )

//Reset leadup count on suspension
if suspension and bearish_leadup == leadupLen and high >= max_close_suspension
    bearish_leadup := na

//Display levels
if showBullishLeadupLvl
    display_level(complete_bullish_leadup, low, bullishLeadupLvlLast, bullishLeadupLvlCss)

if showBearishLeadupLvl
    display_level(complete_bearish_leadup, high, bearishLeadupLvlLast, bearishLeadupLvlCss)

//---------------------------------------------------------------------------------------------------------------------}